Spring Security 프로젝트 설정 3 - Security Config
✒️ 2025-05-28 14:21 내용 수정
-
가이드 영상 : Amigoscode's Spring Boot 3 + Spring Security 6 - JWT Authentication and Authorisation
-
Spring Security의 가이드 영상을 찾아보던 중 위 영상이 도움이 되어 영상 내용을 직접 따라하며 내용을 정리하였다.
- 이전에 Spring Security 5.x 버전 영상을 참고했다가 Spring Security Config에서 많은 부분이 달라져서 Spring Security 6.x 버전 영상을 다시 찾아 정리했다.
-
Spring Security 프로젝트 설정 1 - DB연결과 JPA 설정, Spring Security 프로젝트 설정 2 - JwtService와 Filter 설정에서 이어진다.
-
SpringSecurity 프로젝트 설정 목록
- Spring Security 기본 사용자 추가 및 테스트
- Spring Security 프로젝트 설정 1 - DB연결과 JPA 설정
- Spring Security 프로젝트 설정 2 - JwtService와 Filter 설정
- Spring Security 프로젝트 설정 3 - Security Config
- Spring Security 프로젝트 설정 4 - Authentication Service와 Controller
- Spring Security 프로젝트 설정 5 - Security CORS 설정
- Spring Security 프로젝트 설정 6 - JWT Refresh Token 생성 및 저장
- Spring Security 프로젝트 설정 7 - JWT Refresh Token 재발급
- Spring Security 프로젝트 설정 8 - JWT 클라이언트 저장
- Spring Security 프로젝트 설정 9 - JWT 로그아웃
- Spring Security 프로젝트 설정 10 - 권한 설정
흐름

Config 추가
config패키지에ApplicationConfig클래스를 추가한다.
csrf().disable()설정을 하는 이유 : JWT를 사용하면 session은 stateless로 관리하기 때문에csrf설정을 굳이 할 필요가 없다.- stateless여야 하는 이유는
Filter를 거치면서 인증을 거쳐야 하므로 session에 state가 저장되면 안된다. - 마찬가지로 session을 설정할 때 반드시
SessionCreationPolicy.STATELESS로 설정해야 한다. - 참고 자료 : 개발자 유미의 SecurityConfig
- stateless여야 하는 이유는
- Spring Security 프로젝트 설정 4 - Authentication Service와 Controller에서 Controller 설정을 진행하며
http.requestMatchers("/")부분을 수정하였다.
package com.example.security.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
http
// session stateless로 인해 꺼 둠
.csrf((auth)->auth.disable())
.authorizeRequests()
.requestMatchers("/") // 나열된 요청들은
.permitAll() // 모두 허용
.anyRequest() // 그 외의 모든 요청은
.authenticated() // 인증 필요
.and()
.sessionManagement((session)->
session // session state는 저장되면 안되므로 stateless로 설정
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter,
UsernamePasswordAuthenticationFilter.class); // jwt 필터 가동
return http.build();
}
}
AuthenticationProvider와AuthenticationManager를 사용하기 위해 각 객체의 Bean을 추가한다.- 비밀번호 암호화를 위한
PasswordEncoder도 추가한다.
- 비밀번호 암호화를 위한
package com.example.security.config;
import com.example.security.user.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@RequiredArgsConstructor
public class ApplicationConfig {
private final UserRepository userRepository;
@Bean
public UserDetailsService userDetailsService() {
return username -> userRepository.findByEmail(username)
.orElseThrow(()->new UsernameNotFoundException("User not found"));
}
// 데이터 접근 역할 - UserDetail 정보 접근 등
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
// 사용할 UserDetailService 지정 - 이미 생성해둔 userDetailsService 사용
authProvider.setUserDetailsService(userDetailsService());
// 비밀번호 암호화 지정
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
// AuthenticationManager 추가
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration config
)
throws Exception{
return config.getAuthenticationManager();
}
// 비밀번호 암호화
@Bean
public PasswordEncoder passwordEncoder() {
// Bcrypt encoder 반환
return new BCryptPasswordEncoder();
}
}
AuthenticationProvider bean 관련 warning
- 20241110 update : 이 warning을 해결할 방법을 찾지 못했다.
- 위와 동일한 코드로
ApplicationConfig를 작성하고 애플리케이션을 실행하면 아래와 같은 warning이 발생한다.
[ restartedMain] r$InitializeUserDetailsManagerConfigurer :
Global AuthenticationManager configured with an AuthenticationProvider bean.
UserDetailsService beans will not be used for username/password login.
Consider removing the AuthenticationProvider bean.
Alternatively, consider using the UserDetailsService in a manually instantiated DaoAuthenticationProvider.
- warning에서는 UserDetailsService bean이 username/password 로그인에 사용되지 않으므로
AuthenticationProviderbean을 제거하거나, 수동으로 인스턴스화된DaoAuthenticationProvider에서UserDetailsService를 사용하는 것을 권장하고 있다. - 그러나 몇몇 방법을 시도해봤을 때 다음과 같은 문제가 발생했다.
AuthenticationProviderBean을 제거했더니 Bean을 못 찾는다는 에러가 발생하며 애플리케이션 실행이 안된다.AuthenticationProvider를 사용하는SecurityConfig에서 에러가 발생한 것 같았다.
@Configuration
@RequiredArgsConstructor
public class ApplicationConfig {
private final UserRepository userRepository;
// Bean을 제거했더니 SecurityConfig에서 문제 발생
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
// 사용할 UserDetailService 지정 - 이미 생성해둔 userDetailsService 사용
authProvider.setUserDetailsService(userDetailsService());
// 비밀번호 암호화 지정
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
}
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
http
// ...
// 여기서 문제가 발생
.authenticationProvider(authenticationProvider)
}
}
ApplicationConfig에 있던AuthenticationProvider,AuthenticationManager,userDetailsService()을 모두SecurityConfig에 옮겨서 수정했더니 이번엔SecurityConfig,JwtAuthenticationFilter간의 순환 참조 문제가 발생했다.UserDetailsService가 필요한 메소드들이 두 클래스 간에 맞물리면서 발생했다.
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final UserRepository userRepository;
// 사용자 조회 - UserDetailsService이 엮인 순환 참조 문제가 발생함
@Bean
public UserDetailsService userDetailsService() {
return username -> userRepository.findByEmail(username)
.orElseThrow(()->new UsernameNotFoundException("User not found"));
}
// 데이터 접근 역할 - UserDetail 정보 접근 등
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
// 사용할 UserDetailService 지정 - 이미 생성해둔 userDetailsService 사용
authProvider.setUserDetailsService(userDetailsService());
// 비밀번호 암호화 지정
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
// AuthenticationManager 추가
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
throws Exception{
return config.getAuthenticationManager();
}
// 비밀번호 암호화
@Bean
public PasswordEncoder passwordEncoder() {
// Bcrypt encoder 반환
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
http
// ...
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthFilter,
UsernamePasswordAuthenticationFilter.class); // jwt 필터 가동
// ...
return http.build();
}
// ...
}
-
순환 참조 문제까지 발생해버려 구글에서 관련 내용을 검색했으나 관련 문제에 대한 해결법으로 Bean 삭제 이야기가 나왔다.
- 이 Bean 삭제가 내가 생각한
@BeanAnnotation을 삭제하는게 아닌 다른 방법인지 이해하지 못해 결국 이 warning을 해결하지 못했다.
- 이 Bean 삭제가 내가 생각한
-
이 문제는 공식 문서의
DaoAuthenticationProvider의 동작 흐름에서 나중에 해결 방법을 알아낼 수 있을 것 같다.- 공식 문서 : DaoAuthenticationProvider
- 공식 문서 : DaoAuthenticationProvider